
ENT.Type = "anim"
ENT.Base = "base_gmodentity"

ENT.PrintName = "Standard PC"
ENT.Author = "[GU]|ZLC|thomasfn"
ENT.Contact = "Don't."
ENT.Instructions = "Spawn it, then press e to use it."
ENT.Purpose = ""

ENT.Spawnable = false
ENT.AdminSpawnable = false

ENT.IsPC = true
ENT.IsLaptop = false

if (SERVER) then

	ENT.ItemModel = "models/props/cs_office/computer_monitor.mdl"
	util.PrecacheModel( ENT.ItemModel )

	AddCSLuaFile( "shared.lua" )
	
	ENT.NextUse = 0
	
	function ENT:ChangeModel( model )
		self.ItemModel = model
		
		self.Entity:SetModel(self.ItemModel)
		self.Entity:PhysicsInit(SOLID_VPHYSICS)
		self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
		self.Entity:SetSolid(SOLID_VPHYSICS)
	end

	function ENT:Initialize()
		self.Entity:SetModel(self.ItemModel)
		self.Entity:PhysicsInit(SOLID_VPHYSICS)
		self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
		self.Entity:SetSolid(SOLID_VPHYSICS)
		
		if (!self.Data) then self.Data = {} end
		if (!self.Data[self:EntIndex()]) then self.Data[self:EntIndex()] = {} end
		
		PCBeam:ResetEntity( self )
		
		self.Data[self:EntIndex()].Status = 0
		self.Data[self:EntIndex()].Router = nil
		self.Data[self:EntIndex()].IP = "192.168.0.1"
		self.Data[self:EntIndex()].ProgramList = {}
		self.Data[self:EntIndex()].Online = false
		self.Data[self:EntIndex()].PortList = {}
		self.Data[self:EntIndex()].PName = ""
		self.Data[self:EntIndex()].WindowName = ""
		self.Data[self:EntIndex()].P_IP = 1
		
		self:PopulateFiles()
		
		PData[self:EntIndex()] = {}
		
		self:InstallProgram( "MyComputer" )
		self:InstallProgram( "Console" )
		self:InstallProgram( "FileViewer" )
		self:InstallProgram( "Pinger" )
		self:InstallProgram( "AChat" )
		
		self:UpdateInt( "OS_DesktopR", 128 )
		self:UpdateInt( "OS_DesktopG", 128 )
		self:UpdateInt( "OS_DesktopB", 255 )
		
		self:UpdateInt( "Status", 0 )
		
		// self:AddFile( "/Documents", "test.txt", "Test File!" )
		
		// self.Data[self:EntIndex()].Files[ "root/Documents" ]["test.txt"] = "Test File!!"
		
		self:OpenPort( 50, "ent" ); -- Ping Port
		self:OpenPort( 73, "ent" ); -- Whois lookup port
		
		self:SetTheme( "DefaultBlue" )
		
		local phys = self.Entity:GetPhysicsObject()
		if (phys && phys:IsValid()) then phys:Wake() end
		
		PCBeam:BeamVarsToAll( self )
	end
	
	function ENT:Use( activator, ply )
		if (CurTime() < self.NextUse) then return end
		self.NextUse = CurTime() + 1
		umsg.Start( "_pcmod_openscreen", ply )
		umsg.Short( self:EntIndex() )
		umsg.String( self.Data[self:EntIndex()].PName )
		umsg.String( self.Data[self:EntIndex()].WindowName )
		umsg.String( self.Data[self:EntIndex()].Theme )
		umsg.End()
		self:BeamPList( ply )
		PCBeam:BeamVars( self, ply )
		ply.CurrentPC = self
		self.Data[self:EntIndex()].PlayerObj = ply
		if (self.Data[self:EntIndex()].CP) then self.Data[self:EntIndex()].CP:PC_Changed( ply, self ) end
	end
	
	function ENT:MakeLaptop( wireless )
		self:ChangeModel( "models/laptop/laptop.mdl" )
		self.IsLaptop = true
		self.Wireless = wireless
		PCBeam:SetValue( self, "Laptop", true )
		PCBeam:SetValue( self, "Wireless", wireless )
	end
	
	function ENT:HasProgram( Name )
		for i=1,#self.Data[self:EntIndex()].ProgramList do
			if (self.Data[self:EntIndex()].ProgramList[i] == Name) then return true end
		end
	end
	
	function ENT:InstallProgram( Name )
		if (self:HasProgram( Name )) then return end
		table.insert( self.Data[self:EntIndex()].ProgramList, Name )
		self:AddDirectory( "/Programs/", Name )
		self:AddFile( "/Programs/" .. Name, Name .. ".prog", "Run " .. Name )
	end
	
	function ENT:LoadProgram( ply, Name )
		if (!self:HasProgram( Name )) then return end
		if (self.Data[self:EntIndex()].CP) then self:UnloadProgram() end
		local prog = ProgramList[ Name ]
		if (prog) then
			self.Data[self:EntIndex()].CP = ProgramList[ Name ]
			self.Data[self:EntIndex()].PName = Name
			self.Data[self:EntIndex()].CP:Initialize( ply )
			self.Data[self:EntIndex()].CP.LPlayer = ply
			PCBeam:SetValue( self, "ProgramLoaded", true )
			PCBeam:SetValue( self, "ProgName", Name )
		else
			ply:PrintMessage( HUD_PRINTTALK, "> Program failed to load! (" .. Name .. ") <\n" )
		end
	end
	
	function ENT:UnloadProgram( )
		if (!self.Data[self:EntIndex()].CP) then return end
		self.Data[self:EntIndex()].CP:Unload()
		self.Data[self:EntIndex()].CP = nil
		self.Data[self:EntIndex()].PName = ""
		self.Data[self:EntIndex()].WindowName = ""
		PCBeam:SetValue( self, "ProgramLoaded", false )
		PCBeam:SetValue( self, "ProgName", "" )
	end
	
	function ENT:DoPacket( Packet )
		// This is called when a packet has been aimed at a port opened by the OS
		port = Packet[3]
		if (Packet[4][1] == "ping") and (port == 50) then
			// We have been pinged!
		end
		if (Packet[4][1] == "whois") and (port == 73) then
			// We have a request for the user.
			local user = "Nobody"
			if (self.Data[self:EntIndex()].PlayerObj) then user = self.Data[self:EntIndex()].PlayerObj:Nick() end
			local result = self:SendPacket( {self.Data[self:EntIndex()].IP, Packet[1], 72, { "whois_response", user } } )
		end
	end
	
	function ENT:OpenPort( port, command )
		if (self:PortOpen( port )) then return end
		table.insert( self.Data[self:EntIndex()].PortList, {port,command} )
	end
	
	function ENT:ClosePort( port )
		if (!self:PortOpen( port )) then return end
		ports = self.Data[self:EntIndex()].PortList
		for i=1,#ports do
			if (ports[i][1] == port) then
				table.remove( self.Data[self:EntIndex()].PortList, i )
			end
		end
	end
	
	function ENT:PortOpen( port )
		ports = self.Data[self:EntIndex()].PortList
		for i=1,#ports do
			if (ports[i][1] == port) then return true end
		end
	end
	
	function ENT:GetPortCommand( port )
		ports = self.Data[self:EntIndex()].PortList
		for i=1,#ports do
			if (ports[i][1] == port) then return ports[i][2] end
		end
		return "none"
	end
	
	// DeliveryCode:
	// 0 = Cannot Deliver
	// 1 = Delivered
	// 2 = Destination not found
	// 3 = No Network
	// 4 = Packet Denied: Closed Port
	// 5 = Packet Denied: Blocked IP
	// 6 = Packet Denied: Invalid packet

	function ENT:SendPacket( Packet )
		if (!Packet) then return 6 end
		dest = Packet[2]
		if (!dest) then return 6 end
		if (dest == "127.0.0.1") then
			return self:PacketDelivered( Packet )
		end
		if (!self.Data[self:EntIndex()].Router) or (!self.Data[self:EntIndex()].Online) then return 3 end
		return self.Data[self:EntIndex()].Router:DeliverPacket( Packet )
	end
	
	function ENT:PacketDelivered( Packet )
		// Lets not accept any packets if we are not on
		if (self.Data[self:EntIndex()].Status != 3) then return 2 end
		// First we deal with the packet header
		if (!Packet) then return 6 end
		source = Packet[1]
		dest = Packet[2]
		port = Packet[3]
		body = Packet[4]
		myip = self.Data[self:EntIndex()].IP
		if (!self:PortOpen(port)) then return 4 end
		if (dest != myip) and (!((dest == "127.0.0.1") and (source == myip))) then return 0 end
		if (self:IPBlocked(source)) then return 5 end
		// Now we deal with the packet body
		com = self:GetPortCommand( port )
		if (com == "none") then
			return 1
		end
		if (com == "prog") then
			if (self.Data[self:EntIndex()].CP) then self.Data[self:EntIndex()].CP:PacketRecieved( Packet, self ) end
			return 1
		end
		if (com == "ent") then
			self:DoPacket( Packet )
			return 1
		end
	end
	
	function ENT:IPBlocked( IP )
		return false
	end
	
	function ENT:SetTheme( theme, ply )
		if (!theme) then theme = "DefaultBlue" end
		if (theme == "") then theme = "DefaultBlue" end
		self:UpdateStr( "Theme", theme )
		if ((ply) && (ply:IsValid())) then
			umsg.Start( "_pcmod_themeupdate", ply )
				umsg.String( theme )
			umsg.End()
		end
	end
	
	function ENT:Think()
		if (self.Data[self:EntIndex()].Status == 0) then return end
		local router = self.Data[self:EntIndex()].Router
		if (self.Wireless) then
			local r = PCMod.WirelessRange
			if ((router) && (router:IsValid())) then
				// We have a router!
				// Check its close enough
				if ((self.Entity:GetPos() - router:GetPos()):Length() > r) then
					// It's too far away!
					router:UnregisterPC( self )
					self.Data[self:EntIndex()].Router = nil
				end
			else
				// We DON'T have a router!
				local inrange = ents.FindInSphere( self.Entity:GetPos(), r )
				for k, v in pairs( inrange ) do
					if ((v.IsRouter) && (v.SatRouter)) then
						self.Data[self:EntIndex()].Router = v
					end
				end
			end
		end
		if (self.Data[self:EntIndex()].Online) then
			if (!self.Data[self:EntIndex()].Router) then
				self:UpdateBool( "Online", false )
				self:SetIP( "192.168.0.1" )
			end
		else
			if (self.Data[self:EntIndex()].Router) then
				// Attempt a connection
				local pid = self.Data[self:EntIndex()].P_IP
				ip = self.Data[self:EntIndex()].Router:RegisterPC( self, pid )
				if (ip) then
					self:SetIP( ip )
					self:UpdateBool( "Online", true )
				end
			end
		end
	end
	
	function ENT:EnterPassword( password )
		if (password == self.Password) then
			self:UpdateInt("Status", 3)
		end
	end
	
	function ENT:ExecuteCommand( ply, command, args )
		local data = self.Data[self:EntIndex()]
		if (command == "turn_on") then
			if (data.Status == 0) then
				// We are loading up the PC.
				// If we have no password, push us straight into the desktop
				// Otherwise, push us into password screen
				if (self.Password == "") then
					self:UpdateInt("Status", 3)
				else
					self:UpdateInt("Status", 2)
				end
				
				// Here we need to retrieve information about the network
				if (!data.Router) then
					// We don't have a router.
					self:UpdateBool( "Online", false )
					// self:UpdateInt("Status", 2)
					self:SetIP( "192.168.0.1" )
				else
					// We DO have a router.
					local pid = self.Data[self:EntIndex()].P_IP
					self:UpdateBool( "Online", true )
					self:SetIP( data.Router:RegisterPC( self, pid ) )
					// self:UpdateInt("Status", 2)
				end
			end
		end
		if (command == "turn_off") then
			if (data.Status != 0) then
				self:UpdateInt("Status", 0)
				self:UpdateStr("IP", "")
				self:UpdateBool("Online", false)
				if (data.Router) then
					data.Router:UnregisterPC( self )
				end
			end
		end
		if (command == "enter_password") then
			if (data.Status == 2) then
				if (args[1] == self.Password) then
					self:UpdateInt("Status", 3)
				end
			end
		end
		if (command == "load_prog") then
			if (data.Status == 3) then
				if (self.Data[self:EntIndex()].CP) then
					UnloadProgram( ply )
				end
				self:LoadProgram( ply, args[1] )
			end
		end
		if (command == "prog_command") then
			if (data.Status == 3) then
				com = args[1]
				table.remove( args, 1 )
				if (self.Data[self:EntIndex()].CP) then
					self.Data[self:EntIndex()].CP:CommandRecieved( ply, com, args )
				end
			end
		end
		if (command == "settheme") then
			self:SetTheme( args[1], ply )
		end
		if (command == "log_off") then
			if (data.Status == 3) then self:UpdateInt( "Status", 2 ) end
		end
		if (command == "setdr") then
			self:UpdateInt( "OS_DesktopR", args[1] )
		end
		if (command == "setdg") then
			self:UpdateInt( "OS_DesktopG", args[1] )
		end
		if (command == "setdb") then
			self:UpdateInt( "OS_DesktopB", args[1] )
		end
		if (command == "dummybeam") then
			PCBeam:SetValue( self, "A_Value", "This is a string!" )
			PCBeam:SetValue( self, "B_Value", 36 )
			PCBeam:BeamVars( self )
		end
		self.Data[self:EntIndex()] = data
	end
	
	function ENT:UpdateInt( var, value )
		self.Data[self:EntIndex()][var] = value
		PCBeam:SetValue( self, var, value )
		if (self.Data[ self:EntIndex() ].PlayerObj) then
			PCBeam:BeamVars( self, self.Data[ self:EntIndex() ].PlayerObj )
		end
	end
	
	function ENT:UpdateBool( var, value )
		self.Data[self:EntIndex()][var] = value
		PCBeam:SetValue( self, var, value )
		if (self.Data[ self:EntIndex() ].PlayerObj) then
			PCBeam:BeamVars( self, self.Data[ self:EntIndex() ].PlayerObj )
		end
	end
	
	function ENT:UpdateStr( var, value )
		self.Data[self:EntIndex()][var] = value
		PCBeam:SetValue( self, var, value )
		if (self.Data[ self:EntIndex() ].PlayerObj) then
			PCBeam:BeamVars( self, self.Data[ self:EntIndex() ].PlayerObj )
		end
	end
	
	function ENT:SetIP( IP )
		self.Data[self:EntIndex()].IP = IP
		self:UpdateStr( "IP", IP )
	end
	
	function ENT:BeamPList( ply )
		obj = self.Data[self:EntIndex()].ProgramList
		if (!obj) then return end
		umsg.Start( "_pcmod_beamlist", ply )
			umsg.Short( #obj )
			for i=1,#obj do
				umsg.String( obj[i] )
			end
		umsg.End()
	end
	
	function ENT:PopulateFiles()
		// Add base folders
		self:AddDirectory( "/", "Programs" )
		self:AddDirectory( "/", "Documents" )
	end
	
	function ENT:AddDirectory( parent, dir )
		local fls = self.Data[ self:EntIndex() ].Files
			if (!fls) then fls = {} end
			fls[ "root" .. parent .. dir ] = {}
		self.Data[ self:EntIndex() ].Files = fls
	end
	
	function ENT:AddFile( dir, filename, content )
		local fls = self.Data[ self:EntIndex() ].Files
			if (!fls) then fls = {} end
			local di = "root" .. dir
			if (!fls[ di ]) then
				// Msg( "Tried to add file to none existant dir '" .. di .. "'!\n" )
				return
			end
			fls[ di ][ filename ] = content
			// Msg( "Added file '" .. filename .. "' to '" .. di .. "'.\n" )
		self.Data[ self:EntIndex() ].Files = fls
	end
end

if (CLIENT) then

	function ENT:Think()
		self.IsLaptop = PCBeam:GetBeamValue( self, "Laptop" )
	end

	function ENT:Draw()
		// Draw our entity
		self.BaseClass.Draw( self )
		
		// Check the status
		local status = PCBeam:GetBeamValue( self, "Status" )
	
		// Declare some variables
		local OF = 0
		local OU = 0
		local OR = 0
		local Res = 0.1
		local RatioX = 1
		local RatioY = 1
		
		// Build the position and angle
		local ang = self.Entity:GetAngles()
		local rot = Vector(-90,90,0)
		
		// Fill them based on the entity's model
		if self.Entity:GetModel() == "models/props_lab/monitor01a.mdl" then
			// Old CRT
			OF = 12.4
			OU = 2.95
			OR = -1.75
			Res = 0.08
			rot = rot + Vector( 4.5, 0, 0 )
		elseif self.Entity:GetModel() == "models/props/cs_office/computer_monitor.mdl" then
			// New LCD
			OF = 3.25
			OU = 15.85
			OR = -2.2
			Res = 0.085
		elseif self.Entity:GetModel() == "models/props/cs_office/TV_plasma.mdl" then
			// LCD Plasma
			OF = 6.1
			OU = 17.05
			OR = -5.99
			Res = 0.175
		elseif self.Entity:GetModel() == "models/props_lab/monitor02.mdl" then
			// Old CRT 2 (NOT IN USE)
			OF = 11.1
			OU = 14
			OR = -1.75
			Res = 0.07
			rot = rot + Vector( 7.5, 0, 0 )
		elseif self.Entity:GetModel() == "models/props_lab/monitor01b.mdl" then
			// Small lab monitor
			OF = 6.5
			OU = -0.8
			OR = -0.1
			Res = 0.037
		elseif self.Entity:GetModel() == "models/props_c17/tv_monitor01.mdl" then
			// C17 TV
			OF = 5.4
			OU = 0.0
			OR = 0.6
			Res = 0.055
		elseif self.Entity:GetModel() == "models/blacknecro/tv_plasma_4_3.mdl" then
			// LCD 4-3 from wire mod
			OF = 0.8
			OU = -2
			OR = -6.6
			Res = 0.23
		elseif self.Entity:GetModel() == "models/kobilica/wiremonitorsmall.mdl" then
			// Small monitor from wire mod
			OF = 0.2
			OU = 4.5
			OR = -0.85
			Res = 0.035
		elseif self.Entity:GetModel() == "models/kobilica/wiremonitorbig.mdl" then
			// Big monitor from wire mod
			OF = 0.3
			OU = 11.8
			OR = -2.35
			Res = 0.095
		else
			// Unknown
			return
		end
		
		ang:RotateAroundAxis(ang:Right(), 	rot.x)
		ang:RotateAroundAxis(ang:Up(), 		rot.y)
		ang:RotateAroundAxis(ang:Forward(), rot.z)
		
		local pos = self.Entity:GetPos() + (self.Entity:GetForward() * OF) + (self.Entity:GetUp() * OU) + (self.Entity:GetRight() * OR)
		
		// Start the 2D 3D
		cam.Start3D2D(pos,ang,Res)
		
		// Some positions
		local x = -151
		local y = -109
		local w = 246 * RatioX
		local h = 191 * RatioY
		
		// Theme
		local theme = PCBeam:GetBeamValue( self, "theme" )
		if (theme == "") then theme = "DefaultBlue" end
		local th = ThemeList[ theme ]
		if (!th) then th = ThemeList[ "DefaultBlue" ] end
		
		// Draw stuff based on status
		if (status == 0) then
			// Off screen
			surface.SetDrawColor(0,0,0,255)
			surface.DrawRect( x, y, w, h )
		end
		if (status == 1) then
			// Booting screen
			surface.SetDrawColor(0,0,50,255)
			surface.DrawRect( x, y, w, h )
		end
		if (status == 2) then
			// Password screen
			surface.SetDrawColor(128,128,255,255)
			surface.DrawRect( x, y, w, h )
			surface.SetDrawColor( 50, 50, 50, 128 )
			surface.DrawRect( x+(w*0.25), y+(h*0.3), w*0.5, h*0.3 )
			draw.SimpleText( "Password Required", "SmallPCText", x+(w/2), y+(h*0.35), Color(255, 255, 255, 255), 1, 1 )
			
			// We can't 2D 3D derma controls (yet :P), so do it ourselves
			surface.SetDrawColor( 255, 255, 255, 255 )
			surface.DrawRect( x+(w*0.3), y+(h*0.45), w*0.4, h*0.05 )
			surface.SetDrawColor( 128, 128, 128, 255 )
			surface.DrawRect( x+(w*0.3), y+(h*0.5), w*0.4, h*0.05 )
			draw.SimpleText( "Login", "PCText", x+(w*0.5), y+(h*0.525), Color( 255, 255, 255, 255 ), 1, 1 )
		end		
		if (status == 3) then
			// Main screen
			
			// Draw desktop
			local r = PCBeam:GetBeamValue( self, "OS_DesktopR", 128 )
			local g = PCBeam:GetBeamValue( self, "OS_DesktopG", 128 )
			local b = PCBeam:GetBeamValue( self, "OS_DesktopB", 255 )
			surface.SetDrawColor(r,g,b,255)
			surface.DrawRect( x, y, w, h )
			
			// Draw lower bar
			th:DrawLowerBar( x, y + (h*0.92), w, h * 0.08 )
			
			// Draw start button
			th:DrawStartButton( x, y+(h*0.92), w*0.2, h*0.08, false )
			
			if (PCBeam:GetBeamValue( self, "ProgramLoaded" )) then
				// Get the loaded program
				local proname = PCBeam:GetBeamValue( self, "ProgName" )
				local pro = ProgramList[ proname ]
				
				// Draw the program background
				th:DrawBackground( x+(w*0.05), y+(h*0.05), w*0.9, h*0.85 )
				
				// Draw the title bar
				local txt = "Program"
				if (pro) then
					txt = pro.NiceName .. " - " .. pro.Author
					//draw.SimpleText( txt, "SmallPCText", x+(w*0.05)+2, y+(h*0.05)+2, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP )
				end
				th:DrawProgramBar( x+(w*0.05), y+(h*0.05), w*0.9, h*0.08, txt )
				
				// Tell the program to draw
				if (pro) then
					pro:Paint( x+(w*0.05), y+(h*0.05), w*0.9, h*0.85 )
					//draw.SimpleText( pro.NiceName, "SmallPCText", x+(w/2), y+(h/2), Color( 255, 255, 255, 255 ), 1, 1 )
				end
			end
		end
			
		cam.End3D2D()
	end
	
end
